package de.lmu.ifi.pst.uwejsf.framework;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import javax.script.Bindings;
import javax.script.ScriptContext;
import javax.script.ScriptEngine;

/**
 * Base class for persistence manager classes. Provides a selection mechanism for queries of different languages using
 * a language-prefix. Also contains an default handler for OGNL-queries, for instance creation and for
 * mapping model class names to application class names.
 * 
 * @author Christian Kroiss
 *
 */
public abstract class AbstractPersistenceManager implements PersistenceManager {

	private static Pattern expressionPattern = Pattern
			.compile("(\\s*(\\w+):)?\\s*(.*)");


	/**
	 * Default implementation of {@link #createObject(String)} that uses reflection to call the
	 * default constructor of the given class.
	 * 
	 * @param className the qualified model class name of the class to instantiate.
	 */
	@Override
	public Object createObject(String className) {
		Class<?> cl = getApplicationClassByModelQName(className);
		Object o = null;
		try {
			o = cl.newInstance();
		} catch (Exception e) {
			throw new UWEJSFRuntimeException(
					"Unable to create instance of model type " + className
							+ ".", e);
		}
		return o;
	}

	

	/**
	 * This method is called when the language of the query expression is not OGNL. If a language was
	 * specified using the language prefix (e.g. JPA: "EXP" ), the language identifier is given in the
	 * parameter <code>expressionLanguage</code>.<br/>
	 * 
	 * NOTE: OGNL-expressions should not be handled here.
	 * 
	 * @param elementClassName the qualified model class name of elements of the query result. 
	 * @param expression the query expression.
	 * @param expressionLanguage the query expression language.
	 * 
	 * @return the result list.
	 */
	protected abstract List<?> queryByExpressionLanguage(
			String elementClassName, String expression,
			String expressionLanguage);

	@Override
	public final List<?> query(String elementClassName, String expression,
			String navigationNode, Map<String, Object> parameters) {
		return query(elementClassName, expression, navigationNode, parameters,
				AbstractUWEContextFactory.getCurrentInstance());

	}

	@Override
	public final List<?> query(String elementClassName, String expression,
			String navigationNode, Map<String, Object> parameters,
			ExpressionContextProvider contextProvider) {
		String language = null;
		String exp = null;

		if (expression == null)
			throw new UWEJSFRuntimeException("No expression given for query!");

		Matcher matcher = expressionPattern.matcher(expression);
		if (!matcher.matches() || matcher.groupCount() != 3)
			throw new UWEJSFRuntimeException(
					"Invalid expression format given for query!");

		language = matcher.group(2);
		exp = matcher.group(3);

		if (language != null && language.equalsIgnoreCase("OGNL"))
			return queryOGNL(exp, parameters, contextProvider);
		else
			return queryByExpressionLanguage(elementClassName, exp, language);
	}

	
	/**
	 * Evaluates an OGNL query with the usual script context created in
	 * {@link AbstractUWEContextFactory#createScriptContext(Object)}.
	 * 
	 */
	protected final List<?> queryOGNL(String expression,
			Map<String, Object> parameters,
			ExpressionContextProvider contextProvider) {

		ScriptContext scriptContext = contextProvider
				.createScriptContext(new Object());
		Bindings bindings = scriptContext
				.getBindings(ScriptContext.ENGINE_SCOPE);
		bindings.putAll(parameters);
		
		ScriptEngine engine = contextProvider.getScriptEngineByName("ognl");

		Object result = null;
		try {
			result = engine.eval(expression, scriptContext);
		} catch (Exception e) {
			throw new UWEJSFRuntimeException("Couldn't evaluate OGNL script: "
					+ expression, e);
		}

		if (result instanceof List)
			return List.class.cast(result);
		else {
			List<Object> retList = new ArrayList<Object>();
			retList.add(result);
			return retList;
		}

	}

	

	@Override
	public String getModelQNameString(String javaClassName) {
		return AbstractUWEContextFactory.getCurrentInstance().getTypeMapping(
				javaClassName);
	}

	@Override
	public Class<?> getApplicationClassByModelQName(String modelQName) {
		String applicationClassName = AbstractUWEContextFactory
				.getCurrentInstance().getReverseTypeMapping(modelQName);
		Class<?> cl = null;
		try {
			cl = Class.forName(applicationClassName);
		} catch (ClassNotFoundException e) {
			throw new UWEJSFRuntimeException("Unable to map model type "
					+ modelQName + ".", e);
		}
		return cl;
	}

}
